#ifndef BISTREAM_H
#define BISTREAM_H

#include <fstream>
#include "../typedefs.hpp"
#include <string>
using namespace std;

class BitStream {

private:
	//Indica en que numero de bit del byte actual estamos
	unsigned char bit_pos;
	//byte actual que se modifica bit a bit, cuando byte pos = 7, se resetea
	unsigned char byte;
	//El stream que usa como base
	std::fstream f;
	//Tamano del archivo.
	EnteroLargo size;

	string name;

public:

	BitStream(FileName filename,
			fstream::openmode mode = fstream::in | fstream::out) {
		f.open(filename, mode);
		bit_pos = 0;
		byte = 0;
		//supuestamente calcula el tamanio.
		name = string(filename);
		EnteroLargo end;
		size = this->f.tellg();
		this->f.seekg(0, f.end);
		end = this->f.tellg();
		f.seekg(0, f.beg);
		size = end - size;
	}

	BitStream() {
		bit_pos = 0;
		byte = 0;
		std::fstream f;
	}

	virtual ~BitStream() {
		if (f.is_open())
			this->close();
	}


	//funciones clasicas del fstream
	void open(const char *filename,
			fstream::openmode mode = fstream::in | fstream::out) {
		f.open(filename, mode);
//		this->name = string(filename);
	}

	void close() {
		this->fillByte();
		f.close();
	}

	void read(char* s, streamsize n) {
		f.read(s, n);
	}

	void write(const char* s, streamsize n) {
		f.write(s, n);
	}

	void get(char& c) {
		f.get(c);
	}

	int get() {
		return f.get();
	}

	void put(unsigned char c) {
		this->size++;
		f.put(c);
//		if(name =="archIndice2"){
//			cout<< c<< endl;
//		}
	}

	bool good() const {
		return f.good();
	}

	bool eof() const {
		return f.eof();
	}

	bool is_open() const {
		return f.is_open();
	}

	void clear() {
		f.clear();
	}

	void seekp(streampos pos) {
		bit_pos = 0;
		byte = 0;
		f.seekp(pos,f.beg);
	}

	std::streampos tellp() {
		return f.tellp();
	}

	void seekg(streampos pos) {
		bit_pos = 0;
		byte = 0;
		f.seekg(pos, f.beg);
	}

	void seekgFinal(streampos pos){
		bit_pos = 0;
		byte = 0;
		f.seekg(pos, f.end);
	}

	std::streampos tellg() {
		return f.tellg();
	}

	//funciones adicionales
	bool getBit() {

		if (bit_pos == 0) {
			byte = (unsigned char) this->get();
		}
		int shiftcant = (7 - bit_pos);
		int shift = (1 << shiftcant);
		bit_pos++;
		if (shiftcant == 0)
			bit_pos = 0;
//		cout<< (byte & shift ? "1": "0");
		return (byte & shift);

	}

	//*****************SOLO PASAR 1 O 0, SINO SE CAGA EL ESTOFADO
	//escribir un bit
	void putBit(int bit) {
		int shiftam = (7 - bit_pos);
		int bitshifteado = bit << shiftam;
		byte = (byte + bitshifteado);
		this->bit_pos++;
		if (bit_pos == 8) {
			this->put(byte);
			bit_pos = 0;
			byte = 0;
		}
//		cout<< (bit > 0 ? "1": "0");
	}

	//rellenar lo que falta del byte con ceros y escribirlo
	void fillByte() {
		//solo rellena si la posicion es mayor a cero, sino escribe un byte vacio, no interesa eso.
		if (bit_pos > 0) {
			this->put(byte);
			bit_pos = 0;
			byte = 0;
		}
		// se deja el byte incompleto relleno con 0s
	}
	EnteroLargo getSize() {
		return this->size;
	}

	//hay un bit mas
	bool hasNextBit() {
		return ((this->size - 1) - f.tellg()) > 0 || this->bit_pos < 8;
	}

	bool hasNext() {
		EnteroLargo pos = f.tellg();
		return ((this->size) - pos) > 0;
	}
	void omitByte() {
		this->bit_pos = 0;
	}
};

#endif // BISTREAM_H
